/** \file gu_vertex_buffer.h

*/

#include "gu_vertex_buffer.h"
#include "gu_util.h"

#include <GL/glew.h>

#include "gr_algorithms.h"

#include <vector>
#include <memory>
#include <functional>
#include <cassert>

namespace gu {

struct VertexParameter
{
    GLint size;
    GLenum type;
    uint32_t location;
    gr::StreamChannelInfo signature;

    size_t getSize()
    {
        size_t result = 0;
        switch(signature.channelType)
        {
            case gr::STREAM_FLOAT4:
                result = sizeof(float) * 4;
            default: break;
        }
        return result;
    }
};

VertexParameter vertexParameterFromSignature(gr::StreamChannelInfo& signature, size_t location)
{
    VertexParameter param = {0,0,location,signature};
    switch(signature.channelType)
    {
        case gr::STREAM_FLOAT4:
        {
            param.size = 4;
            param.type = GL_FLOAT;
            break;
        }
        default: break;
    }
    return param;
}


class VertexBufferGL : public VertexBuffer
{
public:

    VertexBufferGL()
    {
        glGenBuffers(1, &buffer_);
        guCheckGlError("VertexBufferGL");
    }
    ~VertexBufferGL()
    {
        for(auto p = parameters_.begin(); p != parameters_.end(); ++p)
        {
            glDisableVertexAttribArray(p->location);
        }
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glDeleteBuffers(1, &buffer_);
        guCheckGlError("~VertexBufferGL");
    }

    //void setSignature(StreamChannelInfo* signature, int count, size_t vertexSize, std::function<uint32_t(size_t)> getLocationFromOffset) override
    void updateParameters(LocMap& locMap) override
    {
        gr::StreamSignature& signature = data_.signature();
        //size_t offset = 0;
        for(auto vertex = signature.begin(); vertex != signature.end(); ++vertex)
        {
            auto location = gr::tryGetValue(locMap, vertex->name);
            if(location)
            {
                auto params = vertexParameterFromSignature(*vertex, *location);
                parameters_.push_back(params);
            }
            else
            {
                // TODO: Raise a proper exception here.
                assert(!"Error mapping");
            }
        }
    }

    bool init() override
    {
        bool result = false;
        size_t dataSize = data_.size();

        if(dataSize)
        {
            glBindBuffer(GL_ARRAY_BUFFER, buffer_);
            glBufferData(GL_ARRAY_BUFFER, dataSize, data_.data(), GL_STATIC_DRAW);

            result = guCheckGlError("VertexBufferGL::init");
        }
        return result;
    }

    void detach() override
    {
        for(auto p = parameters_.begin(); p != parameters_.end(); ++p)
        {
            glDisableVertexAttribArray(p->location);
        }
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    bool use() override
    {
        bool result = true;
        size_t vertexSize = data_.sampleSize();
        glBindBuffer(GL_ARRAY_BUFFER, buffer_);

        for(auto p = parameters_.begin(); p != parameters_.end(); ++p)
        {
            glVertexAttribPointer(p->location, p->size, p->type, GL_FALSE, vertexSize, reinterpret_cast<GLvoid*>(p->signature.offset));
            glEnableVertexAttribArray(p->location);
        }
        
        result = guCheckGlError("VertexBufferGL::use");
        return result;
    }

    uint32_t id() override
    {
        return buffer_;
    }

    gr::StreamData& streamData()
    {
        return data_;
    }

private: 
    GLuint buffer_;
    std::vector<VertexParameter> parameters_;
    gr::StreamData data_;
};


/* IndexBuffer */

/** \class IndexBufferGL */
class IndexBufferGL : public IndexBuffer
{
public:
    IndexBufferGL()
    {
        glGenBuffers(1, &buffer_);
    }

    size_t indexCount() override
    {
        return indices_.size();
    }

    bool init() override
    {
        bool result = false;
        size_t indexCount = indices_.size();
        if(indexCount)
        {
            uint16_t* data = indices_.data();
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint16_t) * indexCount, data, GL_STATIC_DRAW);
            result = guCheckGlError("IndexBufferGL::init");
        }
        return result;
    }

    void detach() override
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }

    bool use() override
    {
        bool result = false;
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_);
        result = guCheckGlError("IndexBufferGL::use");
        return result;
    }

    uint32_t id() override
    {
        return buffer_;
    }

    gr::StreamGeometryIndexData& indexData()
    {
        return indices_;
    }

private:
    GLuint buffer_;
    gr::StreamGeometryIndexData indices_;
};

VertexBuffer* newGlVertexBuffer()
{
    return new VertexBufferGL();
}

IndexBuffer* newGlIndexBuffer()
{
    return new IndexBufferGL();
}

}
